/**  
 * All rights Reserved, Designed By <a href="www.itttq.com" target="_blank">www.itttq.com</a>
 * @date:   2017-07-21 11:42:21
 * @Copyright: 2017 <a href="www.itttq.com" target="_blank">www.itttq.com</a> Inc. All rights reserved. 
 */
package com.itttq.gshop.pbpm.service;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfigImpl;
import com.github.wxpay.sdk.WXPayConstants.SignType;
import com.github.wxpay.sdk.WXPayUtil;
import com.itttq.gshop.domain.po.OrderInfoPo;
import com.itttq.gshop.domain.po.PayApplyPo;
import com.itttq.gshop.domain.po.PayMethodPo;
import com.itttq.gshop.domain.po.UserInfoPo;
import com.itttq.gshop.domain.to.ResultPageTo;
import com.itttq.gshop.domain.to.ResultTo;
import com.itttq.gshop.generic.util.Constants.OrderOperate;
import com.itttq.gshop.generic.util.Constants.OrderStatus;
import com.itttq.gshop.generic.util.Constants.PayMethod;
import com.itttq.gshop.generic.util.Constants.PayStatus;
import com.itttq.gshop.pbpm.mapper.IOrderInfoMapper;
import com.itttq.gshop.pbpm.mapper.IPayApplyMapper;
import com.itttq.gshop.pbpm.mapper.IPayMethodMapper;
import com.itttq.gshop.pbpm.mapper.IUserInfoMapper;

@Service
public class PayApplyService {
	private static Logger LOG = LoggerFactory.getLogger(PayApplyService.class);
	@Autowired
	private IPayApplyMapper payApplyMapper;
	@Autowired
	private IOrderInfoMapper orderInfoMapper;
	@Autowired
	private IPayMethodMapper payMethodMapper;
	@Autowired
	private OrderInfoService orderInfoService;
	@Autowired
	private IUserInfoMapper userMapper;

	// 微信公众号key，商户秘钥
	@Value("${wx.mp.key}")
	private String key;
	
	/**
	 * 根据ID获取PayApplyPo对象
	 * 
	 * @param id
	 * @return
	 * @Author 迷蝶
	 * @return PayApplyPo
	 */
	public PayApplyPo getPayApply(int id) {
		return payApplyMapper.findPayApplyById(id);
	}
	
	/**
	 * 根据条件获取PayApplyPo对象，并返回查询的数据总数
	 * 
	 * @param payApply
	 * @Author 迷蝶
	 * @return ResultPageTo<PayApplyPo>
	 */
	public ResultPageTo<PayApplyPo> getPayApplys(PayApplyPo payApply) {
		return new ResultPageTo<PayApplyPo>(payApply.getStart(), payApply.getSize(), 
				payApplyMapper.findPayApplyCount(payApply), payApplyMapper.findPayApplys(payApply));
	}
	
	/**
	 * 根据ID删除PayApplyPo对象
	 * 
	 * @param id
	 * @Author 迷蝶
	 */
	public Integer delPayApply(int id) {
		return payApplyMapper.delPayApplyById(id);
	}
	
	/**
	 * 根据订单ID生成支付请求，如果是线下支付，直接为支付成功并更新订单状态
	 * 
	 * @param orderId		订单ID
	 * @param payMethodId		支付方式ID。写死id=1为线下支付
	 * @Author 迷蝶
	 * @return ResultTo	如果操作成功且是微信支付，则msg字段是微信支付成功的json字符，如下：<br>
	 * {"nonce_str":"HkTGq54BjNAD6GJM","appid":"wx24eb520310b782ab","sign":"7A70D411259C0BD96B67DBD51A825EC842D490F3ACB0CC57B565627A55BBCB43",
	 * "trade_type":"JSAPI","return_msg":"OK","result_code":"SUCCESS","mch_id":"1505232361","return_code":"SUCCESS",
	 * "prepay_id":"wx31105517688038df460e8c7f4110380543"}
	 * @throws Exception 
	 */
	@Transactional
	public ResultTo payApply(int orderId, int payMethodId) throws Exception {
		LOG.info("pay apply： orderId = {}, payMethodId = {}", orderId, payMethodId);
		ResultTo rt = new ResultTo();
		OrderInfoPo order = orderInfoMapper.findOrderInfoById(orderId);
		LOG.info("pay apply order info： order = {}", order);
		if(order == null) {
			rt.setCode(ResultTo.ORDER_NON_EXIST);
			return rt;
		}
		// 判断订单是否有支付记录，为以防万一，同一种支付方式，只能发起一次支付请求
//		// 还有处理就是判断订单号是否成功，不成功则重新生成支付请求。 TODO GXG
//		if(null != order.getPayMethodId() && order.getPayMethodId().intValue() == payMethodId) {
//			// 重新发起支付请求，不再新建支付请求。需要记录微信预付单单号 TODO 加字段
//		}
		PayMethodPo payMethod = payMethodMapper.findPayMethodById(payMethodId);
		if(payMethod == null) {
			rt.setCode(ResultTo.PAY_NO_METHOD);
			return rt;
		}
		order.setPayMethodId(payMethodId);
		order.setPayMethodName(payMethod.getName());
		// 生成支付请求
		PayApplyPo payApply = new PayApplyPo();
		payApply.setAmount(order.getAmountTotal());
		// 实际支付金额，暂时没有打折优惠，使用红包等；因此实际支付金额和订单金额一致
		payApply.setAmountActual(order.getAmountTotal());
		payApply.setCreatorId(order.getUserId());
		payApply.setUpdaterId(order.getUserId());
		payApply.setCusId(String.valueOf(order.getUserId()));
		payApply.setCurName(order.getUserName());
		payApply.setOrderCode(order.getCode());
		payApply.setPayMethodId(payMethodId);
		payApply.setPayMethodName(payMethod.getName());
		payApply.setPayMethodIcon(payMethod.getIcon());
		payApply.setRequestTime(new Date());
		/* 1:待付款；2:支付成功；3支付失败；4、支付取消 */
		// 如果是线下支付，修改支付为支付成功，其余为待支付 TODO GXG 此处处理有待商榷
		payApply.setStatus(payMethodId == 1 ? PayStatus.SUCCESS.getVal() : PayStatus.UNPAY.getVal());
		LOG.info("pay apply add： payApply = {}", payApply);
		int rs = payApplyMapper.addPayApply(payApply);
		if(rs != 1) {
			rt.setCode(ResultTo.BUSINESS_ERR);
			return rt;
		}
		// 线下支付直接修改订单为支付成功
		if(payMethodId == 1) {
			rt = orderInfoService.updateOrderStatus(order.getId(), OrderStatus.PAID.name(), 
					OrderOperate.PAY.name(), "线下支付成功", order.getUserId(), order.getUserName());
			if(rt.getCode() != ResultTo.SUCCESS) {
				return rt;
			}
			updateOrderPayInfo(order, payApply.getRequestTime());
		} else if(payMethodId == 3) { // 微信支付
			 // 获取用户信息
	        UserInfoPo user = userMapper.findUserInfoById(order.getUserId());
			LOG.info("pay apply wx, get order user： user = {}", user);
	        if(StringUtils.isBlank(user.getOpenid())) {
	        	rt.setCode(ResultTo.PAY_NO_USER);
	        	return rt;
	        }
	        wxPay(order, payApply, user.getOpenid(), rt);
		}
		return rt;
	}
	
	/**
	 * 微信支付
	 * 
	 * @return
	 * @Author 迷蝶
	 * @return String
	 * @throws Exception 
	 */
	private ResultTo wxPay(OrderInfoPo order, PayApplyPo payApply, String openid, ResultTo rt) throws Exception {
		HashMap<String, String> data = new HashMap<String, String>();
        data.put("body", "订单支付-渝酒坊");
        // 订单号，此处设置为支付ID。是避免重复支付订单号重复的问题。TODO GXG 后续根据实际情况看怎么处理
        data.put("out_trade_no", String.valueOf(payApply.getId()));
        data.put("device_info", "WEB");
        data.put("fee_type", "CNY");
        // 实际支付金额，转换为分
        data.put("total_fee", String.valueOf(payApply.getAmountActual().multiply(new BigDecimal(Double.toString(100))).intValue()));
        data.put("spbill_create_ip", "123.12.12.123");
        // 暂时写死
        data.put("notify_url", "http://h5.wine22.com/wx/notify.html");
        // 设置订单生效时间和过期时间
        DateFormat bf = new SimpleDateFormat("yyyyMMddHHmmss");
    	Calendar cal = Calendar.getInstance();
        data.put("time_start", bf.format(cal.getTime()));
        cal.add(Calendar.HOUR, 1);
        data.put("time_expire", bf.format(cal.getTime()));
        data.put("sign_type", "HMAC-SHA256");
        data.put("trade_type", "JSAPI");
        // 商品ID，此处获取商品订单ID
        data.put("product_id", String.valueOf(order.getId()));
        // 附加信息，去订单ID。微信会透传
        data.put("attach", String.valueOf(order.getId()));
        // 获取用户信息
        data.put("openid", openid);
        
        WXPayConfigImpl config = WXPayConfigImpl.getInstance();
        WXPay wxpay = new WXPay(config);
		LOG.info("pay apply wx request： data = {}", data);
        Map<String, String> r = wxpay.unifiedOrder(data);
		LOG.info("pay apply wx responce： data = {}", r);
		// 校验签名
		if(!WXPayUtil.isSignatureValid(r, key, SignType.HMACSHA256)) {
			LOG.info("pay apply wx signature valid result：false");
			rt.setCode(ResultTo.PAY_ERR_SIGN);
			return rt;
		}
        // 判断返回结果是否成功
        if(r.get("return_code").equalsIgnoreCase("SUCCESS")) {
        	// 响应成功，判断支付结果
        	if(r.get("result_code").equalsIgnoreCase("SUCCESS")) {
        		String ts = String.valueOf(System.currentTimeMillis() / 1000);
        		r.put("timeStamp", ts);
        		// 重新生成签名：appId、timeStamp、nonceStr、package、signType
        		Map<String, String> signMap = new HashMap<String, String>();
        		signMap.put("appId", r.get("appid"));
        		signMap.put("timeStamp", ts);
        		signMap.put("nonceStr", r.get("nonce_str"));
        		signMap.put("package", "prepay_id=" +  r.get("prepay_id"));
        		signMap.put("signType", "HMAC-SHA256");
        		signMap.put("paySign",  WXPayUtil.generateSignature(signMap, key, SignType.HMACSHA256));
        		rt.setCode(ResultTo.SUCCESS);
        		rt.setMsg(JSON.toJSONString(signMap));
        		LOG.info("pay apply wx, return data is: {}", signMap);
        		updateOrderPayInfo(order, payApply.getRequestTime());
        	} else {
        		rt.setCode(ResultTo.PAY_FAIL);
        		rt.setMsg(r.get("return_msg"));
        	}
        } else {
        	rt.setCode(ResultTo.PAY_ERR);
        	rt.setMsg(r.get("return_msg"));
        }
        return rt;
	}
	/**
	 * 修改订单支付信息，TODO GXG 此处可考虑优化，更新了两次订单
	 * 
	 * @param order
	 * @param requestTime 	支付请求时间
	 * @Author 迷蝶
	 * @return void
	 */
	public void updateOrderPayInfo(OrderInfoPo order, Date requestTime) {
		// 同时更新订单支付金额和支付时间
		OrderInfoPo orderInfo = new OrderInfoPo();
		orderInfo.setPayMethodId(order.getPayMethodId());
		orderInfo.setPayMethodName(order.getPayMethodName());
		orderInfo.setAmountPay(order.getAmountTotal());
		orderInfo.setPayTime(requestTime);
		orderInfo.setId(order.getId());
		LOG.info("pay apply wx, update order pay info： orderInfo = {}", orderInfo);
		orderInfoMapper.updateOrderInfo(orderInfo);
	}
	
	/**
	 * 处理支付结果，只校验支付金额是否和支付一致、支付方式，不做通知的签名校验。
	 * 
	 * @param orderId
	 * @param payApplyId
	 * @param payAmount		支付金额，精确到分
	 * @param pm
	 * @param ps
	 * @throws Exception
	 * @Author 迷蝶
	 * @return ResultTo
	 */
	@Transactional
	public ResultTo payResult(Integer orderId, int payApplyId, int payAmount, PayMethod pm, PayStatus ps) {
		LOG.info("支付结果： orderId={}，payId = {}，payAmount={}，payMethod={}-{}，payStatus={}-{}", 
				orderId, payApplyId, payAmount, pm.getVal(), pm.getText(), ps.getVal(), ps.getText());
		ResultTo rt = new ResultTo();
		PayApplyPo apply = payApplyMapper.findPayApplyById(payApplyId);
		// 获取数据库支付记录
		if(null == apply) {
			LOG.info("支付结果： 未找到对应支付记录");
			rt.setCode(ResultTo.PAY_NO_APPLY);
			return rt;
		}
		LOG.info("支付结果： 数据库支付记录：{}", apply.toString());
		// 判断支付类型是否匹配
		if(apply.getPayMethodId().intValue() != pm.getVal()) {
			LOG.info("支付结果： 回调支付方式不匹配。数据库记录支付方式={}，回调支付方式={}", apply.getPayMethodId(), pm.getVal());
			rt.setCode(ResultTo.PAY_NO_METHOD);
			rt.setMsg("支付方式不匹配！");
			return rt;
		}
		// 支付金额，精确到分
		if(apply.getAmountActual().intValue() * 100 != payAmount) {
			LOG.info("支付结果： 回调支付金额不匹配。数据库中金额={}分，回调支付金额={}分", 
					apply.getAmountActual().intValue() * 100, payAmount);
			rt.setCode(ResultTo.PAY_ERR_AMOUNT);
			return rt;
		}
		PayApplyPo payApply = new PayApplyPo();
		payApply.setId(payApplyId);
		payApply.setReceiveTime(new Date());
		// 判断支付状态：
		// 支付成功<-- 支付中、支付失败；
		// 支付失败<-- 支付中
		if(ps.getVal() == PayStatus.SUCCESS.getVal() 
				&& (apply.getStatus().intValue() == PayStatus.UNPAY.getVal() 
				|| apply.getStatus().intValue() == PayStatus.FAIL.getVal())) {
			payApply.setStatus(PayStatus.SUCCESS.getVal());

			LOG.info("支付结果： 修改订单为已支付");
			// 修改订单为成功
			rt = orderInfoService.updateOrderStatus(orderId, OrderStatus.PAID.name(), 
					OrderOperate.PAY.name(), apply.getPayMethodName() + "成功", Integer.parseInt(apply.getCusId()), apply.getCurName());
			LOG.info("支付结果： 修改订单状态结果code={}, msg={}", rt.getCode(), rt.getMsg());
			if(rt.getCode() != ResultTo.SUCCESS) {
				return rt;
			}
		} else if(ps.getVal() == PayStatus.FAIL.getVal() && apply.getStatus().intValue() == PayStatus.UNPAY.getVal()) {
			// 支付失败则不修改订单状态
			payApply.setStatus(PayStatus.FAIL.getVal());
			LOG.info("支付结果： 支付失败");
		} else {
			LOG.info("支付结果： 支付请求状态错误，支付记录状态={}，请求状态={}", 
					apply.getStatus(), ps.getVal());
			rt.setCode(ResultTo.PAY_ERR_STATUS);
			return rt;
		}
		LOG.info("支付结果： 更新支付记录");
		int rs = payApplyMapper.updatePayApply(payApply);
		LOG.info("支付结果： 更新支付记录结果：{}", rs);
		if(rs != 1) {
			rt.setCode(ResultTo.BUSINESS_ERR);
			return rt;
		}
		// 插入支付日志 TODO GXG
		return rt;
	}
	
	/**
	 * 根据条件更新PayApplyPo对象
	 * 
	 * @param payApply
	 * @Author 迷蝶
	 */
	public Integer updatePayApply(PayApplyPo payApply) {
		return payApplyMapper.updatePayApply(payApply);
	}
	
}
